package link.anycar.study.java8;

import link.anycar.study.java8.helper.OneParamFunction;
import link.anycar.study.java8.helper.OneParamWithReturnFunction;
import link.anycar.study.java8.helper.TwoParamWithReturnFunction;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.function.Predicate;

public class Lambda {

    public static void main(String[] args) throws Exception {
        Lambda lambda = new Lambda();
        lambda.syntax();
        lambda.variableLifeSpan();
        lambda.functional();
    }

    private void printSeparator(int index) {
        String caller = Thread.currentThread().getStackTrace()[2].getMethodName();
        System.out.println("\n==========" + caller + ": " + index + "==========");
    }

    public void syntax() throws Exception {
//        (params) -> expression
//        (params) -> {statements;}

        printSeparator(1);
        // one statement
        Runnable r = () -> System.out.println("Hello world from very simple lambda runnable object.");
        r.run();

        printSeparator(2);
        // multiple statement
        r = () -> {
            System.out.println("This is more complicated.");
            System.out.println("As it has more than one statement.");
            System.out.println("In thread: " + Thread.currentThread().getName());
        };
        r.run();

        printSeparator(3);
        // no param with return and only one statement
        Callable<String> simpleCallable = () -> "value from very simple callable";
        System.out.println(simpleCallable.call());

        printSeparator(4);
        // no param with return and multiple statements
        Callable<String> complexCallable = () -> {
            Thread.sleep(50);
            return "value from complex callable";
        };
        System.out.println(complexCallable.call());

        printSeparator(5);
        // one param no return
        OneParamFunction<String> opf = x -> System.out.println("One param method called: " + x);
        opf.action("Hello world.");

        printSeparator(6);
        // one param with return
        OneParamWithReturnFunction<String, Integer> opwrf = x -> Integer.parseInt(x);
        System.out.println(opwrf.action("12345"));

        printSeparator(7);
        // multiple params
        Comparator<Integer> cmp = (x, y) -> x - y;
        List<Integer> ints = Arrays.asList(1, 10, 3, 4, 2, 5, 9, 6, 7, 8);
        System.out.println("original: " + ints);
        ints.sort(cmp);
        System.out.println("after sort: " + ints);

        printSeparator(8);
        // static method reference
        opwrf = Integer::parseInt;
        System.out.println(opwrf.action("12345"));

        printSeparator(9);
        // method reference
        opwrf = "hello world"::indexOf;
        System.out.println(opwrf.action("world"));

        printSeparator(10);
        // constructor reference
        OneParamWithReturnFunction<Runnable, Thread> threadCreator = Thread::new;
        threadCreator.action(r).start();

        Thread.sleep(10);
        printSeparator(11);
        // constructor reference with multiple params
        TwoParamWithReturnFunction<Runnable, String, Thread> threadCreator2 = Thread::new;
        threadCreator2.action(r, "NamedThread").start();

        Thread.sleep(10);
        printSeparator(12);
        // use lambda blocks directly without assign it to a variable
        final String separator = ",";
        Arrays.asList("hello", "world", "here", "we", "come").forEach(x -> {
            System.out.print(x + separator);
        });
        System.out.println();

        printSeparator(13);
        // use lambda blocks directly without assign it to a variable 2
        ints = Arrays.asList(1, 10, 3, 4, 2, 5, 9, 6, 7, 8);
        System.out.println("before sort: " + ints);
        ints.sort((x, y) -> y - x);
        System.out.println("after sort: " + ints);
    }

    public void variableLifeSpan() {
        printSeparator(1);
        String hello = "hello world";
        Runnable r = () -> {
            // only final variable is accessible from lambda blocks, but you can ignore the final key word
            System.out.println(hello);
//            outer enclosure variable should actually be final, so you can't reassign value to it
//            hello = "world";
        };
//        hello = "world"; // this make hello not a final varable and is not visible in r
        r.run();
    }

    public void functional() {
        printSeparator(1);
        List<Integer> ints = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        displayFiltered(ints, x -> true); // display all
        displayFiltered(ints, x -> x % 2 == 0); // even number
        displayFiltered(ints, x -> x % 2 == 1); // odd number

    }

    private <T> void displayFiltered(List<T> list, Predicate<T> predicate) {
        list.forEach(x -> {
            if (predicate.test(x)) {
                System.out.print(x);
                System.out.print(",");
            }
        });
        System.out.println();
    }

}
